﻿/*
 * (c) 2008 MOSA - The Managed Operating System Alliance
 *
 * Licensed under the terms of the New BSD License.
 *
 * Authors:
 *  Michael Ruck (grover) <sharpos@michaelruck.de>
 */

using System;
using iPear.Compiler.Framework;
using iPear.Compiler.Framework.Operands;
using iPear.Compiler.Metadata;
using IR = iPear.Compiler.Framework.IR;

namespace iPear.Platform.x86
{
	/// <summary>
	/// This is an x86 specific compiler stage to remove floating point constants from instructions.
	/// </summary>
	/// <remarks>
	/// The code generated by the x86 compiler is position independent. However x86 does not allow
	/// embedding floating point values as immediate inside the code, so these have to be moved outside
	/// and referenced through a memory offset starting at the 
	/// </remarks>
	public sealed class ConstantRemovalStage : BaseMethodCompilerStage, IMethodCompilerStage, IPlatformStage, IPipelineStage
	{
		#region Data members

		// Check the type of the constant operand against this list of large CIL types,
		// that need special handling.
		private static CilElementType[] _largeCilTypes = {
				CilElementType.R4,
				CilElementType.R8,
				//CilElementType.I8,
				//CilElementType.U8
			};

		/// <summary>
		/// If true it indicates that this compiler stage has moved at least 
		/// one floating point constant to physical memory.
		/// </summary>
		private bool _constantRemoved;

		#endregion // Data members

		#region Properties

		/// <summary>
		/// Gets the existance of FP constants in the instruction stream.
		/// </summary>
		public bool ConstantRemoved
		{
			get { return _constantRemoved; }
		}

		#endregion // Properties

		#region IMethodCompilerStage Members

		/// <summary>
		/// Runs the specified method compiler.
		/// </summary>
		public void Run()
		{
			Context ctxEpilogue = CreateContext(FindBlock(Int32.MaxValue));
			ctxEpilogue.GotoLast();

			// Iterate all blocks and collect locals from all blocks
			foreach (BasicBlock block in basicBlocks)
				ProcessInstructions(CreateContext(block), ctxEpilogue);
		}

		#endregion // IMethodCompilerStage Members

		#region Internals

		/// <summary>
		/// Enumerates all instructions and eliminates floating point constants from them.
		/// </summary>
		/// <param name="ctx">The context.</param>
		/// <param name="ctxEpilogue">The context of the epilogue.</param>
		private void ProcessInstructions(Context ctx, Context ctxEpilogue)
		{
			// Current constant operand
			ConstantOperand co = null;

			for (; !ctx.EndOfInstruction; ctx.GotoNext())
			{
				// A constant may only appear on the right side of an expression, so we ignore constants in
				// result - there should never be one there.
				foreach (Operand op in ctx.Operands)
				{
					co = op as ConstantOperand;
					if (co != null && IsLargeConstant(co))
					{
						// Move the constant out of the code stream and place it right after the code.
						ctxEpilogue.AppendInstruction(Instruction.LiteralInstruction);
						ctxEpilogue.LiteralData = new IR.LiteralData(ctx.Label, co.Type, co.Value);

						op.Replace(((ctxEpilogue.Instruction) as Instructions.LiteralInstruction).CreateOperand(ctxEpilogue), instructionSet);

						_constantRemoved = true;
					}

				}
			}
		}

		/// <summary>
		/// Determines if the given constant operand is a large constant.
		/// </summary>
		/// <remarks>
		/// Only constants, which have special types or do not fit into an immediate argument
		/// are large and are moved to memory.
		/// </remarks>
		/// <param name="co">The constant operand to check.</param>
		/// <returns>True if the constant is large and needs to be moved to a literal.</returns>
		private static bool IsLargeConstant(ConstantOperand co)
		{
			return (Array.IndexOf<CilElementType>(_largeCilTypes, co.Type.Type) != -1);
		}

		#endregion // Internals
	}
}
